package com.github.paicoding.forum.core.mdc;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Slf4j
@Aspect
@Component
public class MdcAspect implements ApplicationContextAware {
    // Spring EL 表达式解析器，用于解析 bizCode 中可能包含的动态表达式（如 #{userId}）
    private ExpressionParser parser = new SpelExpressionParser();
    // 参数名发现器，用于获取目标方法的参数名（配合表达式解析动态参数）
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    // @Pointcut：定义 AOP 的 “切入点”（即哪些方法需要被拦截）
    // @annotation(MdcDot)：拦截方法上直接标记 @MdcDot 的方法（如 @MdcDot(bizCode = "login")
    // @within(MdcDot)：拦截类上标记 @MdcDot 的所有方法（如 @MdcDot(bizCode = "order")
    @Pointcut("@annotation(MdcDot) || @within(MdcDot)")
    public void getLogAnnotation() {
    }

    // 环绕通知：在目标方法执行前后都插入逻辑
    @Around("getLogAnnotation()")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        // 前置：注入 bizCode 到 MDC，返回是否成功注入
        boolean hasTag = addMdcCode(joinPoint);
        try {
            // 执行目标方法
            Object ans = joinPoint.proceed();
            // 返回目标方法的执行结果
            return ans;
        } finally {
            // 后置：无论目标方法成功/失败，都记录耗时并清理 MDC
            log.info("执行耗时: {}#{} = {}ms",
                    // 类名（简化）
                    joinPoint.getSignature().getDeclaringType().getSimpleName(),
                    // 方法名
                    joinPoint.getSignature().getName(),
                    // 耗时（当前时间-开始时间）
                    System.currentTimeMillis() - start);
            if (hasTag) {
                // 若注入过 bizCode，重置 MDC（保留 traceId，清空其他信息）
                MdcUtil.reset();
            }
        }
    }

    // 注入 bizCode 到 MDC
    private boolean addMdcCode(ProceedingJoinPoint joinPoint) {
        // 获取目标方法的签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 优先获取方法上的 @MdcDot 注解；若没有，再获取类上的 @MdcDot 注解
        MdcDot dot = method.getAnnotation(MdcDot.class);
        if (dot == null) {
            dot = (MdcDot) joinPoint.getSignature().getDeclaringType().getAnnotation(MdcDot.class);
        }

        // 若存在 @MdcDot 注解，解析 bizCode 并注入 MDC
        if (dot != null) {
            MdcUtil.add("bizCode", loadBizCode(dot.bizCode(), joinPoint));

            // 标记已注入 MDC
            return true;
        }
        return false;
    }

    private String loadBizCode(String key, ProceedingJoinPoint joinPoint) {
        // 若 bizCode 为空，直接返回空字符串
        if (StringUtils.isBlank(key)) {
            return "";
        }
        // 1. 创建 Spring EL 表达式的上下文（用于存储变量，供表达式解析）
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 2. 给上下文设置 Bean 解析器，支持解析 Spring Bean（如 #{userService.getUserName()}）
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        // 3. 获取目标方法的参数名和参数值，放入上下文
        // 获取参数名（如 ["orderId", "userId"]）
        String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
        // 获取参数值（如 [123, 456]）
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            // 绑定参数名-值到上下文（如 orderId=123）
            context.setVariable(params[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    // Spring 容器上下文，通过 ApplicationContextAware 接口注入
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
